#!/usr/bin/env python3

import sys
import numpy as np
from ctypes import CDLL, c_ubyte, cast, POINTER, byref, memset, sizeof, CFUNCTYPE, c_void_p, py_object
import pdb

from .MvImport import (
    MvCamera,
    MV_GIGE_DEVICE,
    MV_USB_DEVICE,
    MV_ACCESS_Exclusive,
    MV_CC_DEVICE_INFO_LIST,
    MV_CC_DEVICE_INFO,
    MV_FRAME_OUT_INFO_EX,
    MV_CC_PIXEL_CONVERT_PARAM,
    MVCC_INTVALUE,
    MV_TRIGGER_MODE_OFF,
    PixelType_Gvsp_BGR8_Packed
)

# 定义回调函数类型
stFrameInfo = POINTER(MV_FRAME_OUT_INFO_EX)
pData = POINTER(c_ubyte)
FrameInfoCallBack = CFUNCTYPE(None, pData, stFrameInfo, c_void_p)

# 全局相机实例字典，用于在回调函数中查找相机实例
_camera_instances = {}

def _global_callback(pData, pFrameInfo, user_ptr):
    """
    全局回调函数，用于处理所有相机实例的回调
    """
    # user_ptr is a py_object containing the HikCamera instance
    try:
        # 由于我们只有一个相机实例，直接获取第一个
        if _camera_instances:
            camera = list(_camera_instances.values())[0]
            camera._internal_callback(pData, pFrameInfo, user_ptr)
    except Exception as e:
        print(f"全局回调函数错误: {e}")


    
class HikCamera:
    def __init__(self, camera_index=0):
        """
        初始化海康威视相机
        
        Args:
            camera_index (int): 相机索引，默认为0
        """
        # ch:创建相机实例 | en:Create Camera Object
        self.camera_index = camera_index
        self.cam = None
        self.nPayloadSize = 0
        self.data_buf = None
        self.frame = None
        self.ret = None
        self.user_callback = None  # 用户提供的回调函数
        self.callback_registered = False  # 回调函数是否已注册
        self.instance_id = id(self)  # 唯一实例ID
        
        # 注册到全局字典
        _camera_instances[self.instance_id] = self
        
        self.ini_hik()

    def ini_hik(self):
        """
        初始化海康威视相机设备
        """
        MvCamera.MV_CC_Initialize()
        # 获得设备信息
        deviceList = MV_CC_DEVICE_INFO_LIST()
        tlayerType = MV_GIGE_DEVICE | MV_USB_DEVICE
        
        # ch:枚举设备 | en:Enum device
        # nTLayerType [IN] 枚举传输层 ，pstDevList [OUT] 设备列表 
        ret = MvCamera.MV_CC_EnumDevices(tlayerType, deviceList)
        if ret != 0:
            print ("enum devices fail! ret[0x%x]" % ret)
            sys.exit()

        if deviceList.nDeviceNum == 0:
            print ("find no device!")
            sys.exit()

        print ("Find %d devices!" % deviceList.nDeviceNum)
        
        # 选择设备
        # self.camera_index = input("please input the number of the device to connect:")

        # ch:创建相机实例 | en:Create Camera Object
        self.cam = MvCamera()

        # ch:选择设备并创建句柄 | en:Select device and create handle
        # cast(typ, val)，这个函数是为了检查val变量是typ类型的，但是这个cast函数不做检查，直接返回val
        stDeviceList = cast(deviceList.pDeviceInfo[int(self.camera_index)], POINTER(MV_CC_DEVICE_INFO)).contents
        
        ret = self.cam.MV_CC_CreateHandle(stDeviceList)
        if ret != 0:
            print ("create handle fail! ret[0x%x]" % ret)
            sys.exit()
        
        # ch:打开设备 | en:Open device
        ret = self.cam.MV_CC_OpenDevice(MV_ACCESS_Exclusive, 0)
        if ret != 0:
            print ("open device fail! ret[0x%x]" % ret)
            sys.exit()

        # ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera)
        if stDeviceList.nTLayerType == MV_GIGE_DEVICE:
            nPacketSize = self.cam.MV_CC_GetOptimalPacketSize()
            # print(nPacketSize)
            if int(nPacketSize) > 0:
                ret = self.cam.MV_CC_SetIntValue("GevSCPSPacketSize",nPacketSize)
                if ret != 0:
                    print ("Warning: Set Packet Size fail! ret[0x%x]" % ret)
            else:
                print ("Warning: Get Packet Size fail! ret[0x%x]" % nPacketSize)
    
        # ch:设置触发模式为off | en:Set trigger mode as off
        ret = self.cam.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_OFF)
        if ret != 0:
            print ("set trigger mode fail! ret[0x%x]" % ret)
            sys.exit()

        # 从这开始，获取图片数据
        # ch:获取数据包大小 | en:Get payload size
        stParam =  MVCC_INTVALUE()
        #csharp中没有memset函数，用什么代替？
        memset(byref(stParam), 0, sizeof(MVCC_INTVALUE))
        # MV_CC_GetIntValue，获取Integer属性值，handle [IN] 设备句柄  
        # strKey [IN] 属性键值，如获取宽度信息则为"Width"  
        # pIntValue [IN][OUT] 返回给调用者有关相机属性结构体指针 
        # 得到图片尺寸，这一句很关键
        # payloadsize，为流通道上的每个图像传输的最大字节数，相机的PayloadSize的典型值是(宽x高x像素大小)，此时图像没有附加任何额外信息
        
        ret = self.cam.MV_CC_GetIntValue("PayloadSize", stParam)
        if ret != 0:
            print ("get payload size fail! ret[0x%x]" % ret)
            sys.exit()
        #关键句，官方没有这个句子，抓取的图片数据是空的，nCurValue是int64
        self.nPayloadSize = stParam.nCurValue
    
        #  关键句，官方没有这个句子，抓取的图片数据是空的，CSharp中怎么实现这句话。
        self.data_buf = (c_ubyte * self.nPayloadSize)()
    
    def _internal_callback(self, pData, pFrameInfo, user_ptr):
        """
        内部回调函数，处理从相机获取的图像数据
        
        Args:
            pData: 图像数据指针
            pFrameInfo: 图像帧信息指针  
            user_ptr: 用户指针（HikCamera实例）
        """
        try:
            # 获取帧信息
            stFrameInfo = cast(pFrameInfo, POINTER(MV_FRAME_OUT_INFO_EX)).contents
            
            # 处理图像数据
            self._process_callback_image(pData, stFrameInfo)
            
            # 调用用户设置的回调函数
            if self.user_callback is not None and self.frame is not None:
                self.user_callback(self.frame, stFrameInfo, self)
                
        except Exception as e:
            print(f"回调函数执行错误: {e}")
    
    def _process_callback_image(self, pData, stFrameInfo):
        """
        处理回调函数中的图像数据
        
        Args:
            pData: 图像数据指针
            stFrameInfo: 图像帧信息
        """
        try:
            # 设置像素转换参数
            stConvertParam = MV_CC_PIXEL_CONVERT_PARAM()
            memset(byref(stConvertParam), 0, sizeof(stConvertParam))
            
            stConvertParam.enDstPixelType = PixelType_Gvsp_BGR8_Packed
            nConvertSize = stFrameInfo.nWidth * stFrameInfo.nHeight * 3
            
            stConvertParam.nWidth = stFrameInfo.nWidth
            stConvertParam.nHeight = stFrameInfo.nHeight
            stConvertParam.pSrcData = pData
            stConvertParam.nSrcDataLen = stFrameInfo.nFrameLen
            stConvertParam.enSrcPixelType = stFrameInfo.enPixelType
            stConvertParam.pDstBuffer = (c_ubyte * nConvertSize)()
            stConvertParam.nDstBufferSize = nConvertSize
            # 转换像素格式
            ret = self.cam.MV_CC_ConvertPixelType(stConvertParam)
            if ret != 0:
                print("convert pixel fail! ret[0x%x]" % ret)
                return
            # 转换为numpy数组
            img_buff = (c_ubyte * stConvertParam.nDstLen)()
            libc = CDLL("libc.so.6")
            libc.memcpy(byref(img_buff), stConvertParam.pDstBuffer, stConvertParam.nDstLen)
            img_buff = np.frombuffer(img_buff, count=int(stConvertParam.nDstBufferSize), dtype=np.uint8)
            img_buff = img_buff.reshape(stFrameInfo.nHeight, stFrameInfo.nWidth, 3)
            self.frame = img_buff
            
        except Exception as e:
            print(f"图像处理错误: {e}")
        
    def set_callback_function(self, callback):
        """
        设置回调函数
        
        Args:
            callback (function): 回调函数，接收参数为 (frame, frame_info, camera_instance)
                                frame: numpy.ndarray 图像数据
                                frame_info: 图像帧信息
                                camera_instance: HikCamera实例
        """
        self.user_callback = callback
        
        # 使用全局回调函数
        c_callback = FrameInfoCallBack(_global_callback)
        
        # 注册回调函数
        ret = self.cam.MV_CC_RegisterImageCallBackEx(c_callback, None)
        if ret != 0:
            print("register callback fail! ret[0x%x]" % ret)
            sys.exit()
        
        self.callback_registered = True
        print("回调函数注册成功")
        
        # 注册回调函数后开始取流
        ret = self.cam.MV_CC_StartGrabbing()
        if ret != 0:
            print ("start grabbing fail! ret[0x%x]" % ret)
            sys.exit()
        
        print("开始取流成功")

    def read_frame(self):
        """
        读取一帧图像数据
        
        Returns:
            numpy.ndarray: 图像数据，如果获取失败则返回None
        """
        if self.callback_registered:
            print("回调函数已注册，图像数据将通过回调函数获取，无需调用read_frame")
            return None
        
        # 如果没有注册回调函数，需要先开始取流
        if not hasattr(self, '_grabbing_started'):
            ret = self.cam.MV_CC_StartGrabbing()
            if ret != 0:
                print ("start grabbing fail! ret[0x%x]" % ret)
                sys.exit()
            self._grabbing_started = True
        
        stFrameInfo = MV_FRAME_OUT_INFO_EX()
        memset(byref(stFrameInfo), 0, sizeof(stFrameInfo))

        ret = self.cam.MV_CC_GetOneFrameTimeout(self.data_buf, self.nPayloadSize, stFrameInfo, 1000)
        if ret == 0:
            self._distill_image(stFrameInfo)
        else:
            print ("no data[0x%x]" % ret)

    def _distill_image(self, stFrameInfo):
        stConvertParam = MV_CC_PIXEL_CONVERT_PARAM()
        memset(byref(stConvertParam), 0, sizeof(stConvertParam))

        stConvertParam.enDstPixelType = PixelType_Gvsp_BGR8_Packed  # opecv要用BGR，不能使用RGB
        nConvertSize = stFrameInfo.nWidth * stFrameInfo.nHeight* 3

        img_buff = None
        if img_buff is None:
            img_buff = (c_ubyte * stFrameInfo.nFrameLen)()
        # ---
        stConvertParam.nWidth = stFrameInfo.nWidth
        stConvertParam.nHeight = stFrameInfo.nHeight
        stConvertParam.pSrcData = cast(self.data_buf, POINTER(c_ubyte))
        stConvertParam.nSrcDataLen = stFrameInfo.nFrameLen
        stConvertParam.enSrcPixelType = stFrameInfo.enPixelType
        stConvertParam.pDstBuffer = (c_ubyte * nConvertSize)()
        stConvertParam.nDstBufferSize = nConvertSize

        ret = self.cam.MV_CC_ConvertPixelType(stConvertParam)
        if ret != 0:
            print("convert pixel fail! ret[0x%x]" % ret)
            del stConvertParam.pSrcData
            sys.exit()
        else:
            # print("convert ok!!")
            # 转OpenCV
            # 彩色处理
            img_buff = (c_ubyte * stConvertParam.nDstLen)()
            libc = CDLL("libc.so.6")
            libc.memcpy(byref(img_buff), stConvertParam.pDstBuffer, stConvertParam.nDstLen)
            img_buff = np.frombuffer(img_buff, count=int(stConvertParam.nDstBufferSize), dtype=np.uint8)
            img_buff = img_buff.reshape(stFrameInfo.nHeight,stFrameInfo.nWidth,3)
            self.frame = img_buff
    def close(self):
        """
        关闭相机连接并释放资源
        """
        # 从全局字典中移除
        if self.instance_id in _camera_instances:
            del _camera_instances[self.instance_id]
        
        # ch:停止取流 | en:Stop grab image
        ret = self.cam.MV_CC_StopGrabbing()
        if ret != 0:
            print ("stop grabbing fail! ret[0x%x]" % ret)
            del self.data_buf
            sys.exit()
    
        # ch:关闭设备 | Close device
        ret = self.cam.MV_CC_CloseDevice()
        if ret != 0:
            print ("close device fail! ret[0x%x]" % ret)
            del self.data_buf
            sys.exit()
    
        # ch:销毁句柄 | Destroy handle
        ret = self.cam.MV_CC_DestroyHandle()
        if ret != 0:
            print ("destroy handle fail! ret[0x%x]" % ret)
            del self.data_buf
            sys.exit()
    
    def __enter__(self):
        """
        支持上下文管理器
        """
        return self
        
    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        支持上下文管理器自动关闭资源
        """
        self.close()

def main():
    """
    使用示例
    """
    try:
        # 创建相机实例
        with HikCamera(camera_index=0) as camera:
            print("相机初始化成功")
            
            # 读取一帧图像
            camera.read_frame()
            if camera.frame is not None:
                print(f"获取图像成功，图像尺寸: {camera.frame.shape}")
                # 在这里可以处理图像数据
                # 例如：cv2.imshow('Camera', camera.frame)
            else:
                print("获取图像失败")
                
    except Exception as e:
        print(f"相机操作失败: {e}")


if __name__ == "__main__":
    main()


